package org.moelab.trafficlight.incoming.socks5;

import java.net.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.channel.*;
import io.netty.handler.codec.socksx.v5.*;

@ChannelHandler.Sharable
public class Socks5CommandRequestHandler extends SimpleChannelInboundHandler<DefaultSocks5CommandRequest> {

    private static final Logger LOG = LoggerFactory.getLogger(Socks5CommandRequestHandler.class);

    private final Socks5IncomingConfig config;

    public Socks5CommandRequestHandler(Socks5IncomingConfig config) {
        this.config = config;
    }

    @Override
    protected void channelRead0(final ChannelHandlerContext ctx, DefaultSocks5CommandRequest msg) throws Exception {
        ChannelPipeline pipeline = ctx.pipeline();
        pipeline.remove(Socks5CommandRequestDecoder.class.getName());
        pipeline.remove(this);

        Socks5CommandType commandType = msg.type();
        if (Socks5CommandType.CONNECT.equals(commandType)) {
            doConnect(ctx, msg);
        } else if (Socks5CommandType.BIND.equals(commandType)) {
            doBind(ctx, msg);
        } else if (Socks5CommandType.UDP_ASSOCIATE.equals(commandType)) {
            doUdpAssociate(ctx, msg);
        } else {
            if (LOG.isWarnEnabled()) {
                LOG.warn(String.format("unsupported command: %s", commandType.toString()));
            }
            ctx.close();
        }
    }

    private void doConnect(ChannelHandlerContext ctx, Socks5CommandRequest msg) {
        InetSocketAddress address = InetSocketAddress.createUnresolved(msg.dstAddr(), msg.dstPort());
        ChannelFuture outgoingChannelFuture = config.getForwarder().income(config, ctx, address);
        if (outgoingChannelFuture != null) {
            outgoingChannelFuture.addListener((ChannelFutureListener) future -> {
                if (future.isSuccess()) {
                    ctx.writeAndFlush(socks5CommandResponse(msg, true));
                    ChannelPipeline pipeline = ctx.pipeline();
                    pipeline.remove(Socks5ServerEncoder.class.getName());
                } else {
                    ctx.writeAndFlush(socks5CommandResponse(msg, false)).addListener(ChannelFutureListener.CLOSE);
                }
            });
        } else {
            ctx.writeAndFlush(socks5CommandResponse(msg, false)).addListener(ChannelFutureListener.CLOSE);
        }
    }

    private void doBind(ChannelHandlerContext ctx, Socks5CommandRequest msg) {
        //TODO
        if (LOG.isWarnEnabled()) {
            LOG.warn("unsupported command: BIND");
        }
        ctx.close();
    }

    private void doUdpAssociate(ChannelHandlerContext ctx, Socks5CommandRequest msg) {
        //TODO
        if (LOG.isWarnEnabled()) {
            LOG.warn("unsupported command: UDP_ASSOCIATE");
        }
        ctx.close();
    }

    private static Socks5CommandResponse socks5CommandResponse(Socks5CommandRequest request, boolean success) {
        Socks5CommandStatus status = success ? Socks5CommandStatus.SUCCESS : Socks5CommandStatus.FAILURE;
        // bug: 不能使用 DOMAIN 会导致消息无法发出
        Socks5AddressType addressType = Socks5AddressType.IPv4;
        return new DefaultSocks5CommandResponse(status, addressType);
    }
}
